/*******************************************************************************
 * Includes
 ******************************************************************************/
#include "print/wed_print.h"

/*******************************************************************************
 * Static
 ******************************************************************************/
struct Wed_dev          *__g_p_dev_print = NULL;
static char              __g_print_buf[WED_PRINT_BUF_SIZE];
static struct Wed_mutex  __g_print_lock;
static int               __g_is_init;

/*******************************************************************************
 * Macro operate
 ******************************************************************************/
#define ZEROPAD      (1 << 0)    /* 用 0 填充 */
#define SIGN         (1 << 1)    /* unsigned/signed long */
#define PLUS         (1 << 2)    /* 显示数字符号，正数显示 “+”，负数显示 “-” */
#define SPACE        (1 << 3)    /* 正数前加空格 */
#define LEFT         (1 << 4)    /* 左对齐 */
#define SPECIAL      (1 << 5)    /* 特殊进制前缀（0x 等） */
#define LARGE        (1 << 6)    /* use 'ABCDEF' instead of 'abcdef' */

/* \brief 是否是 0-9 */
#define isdigit(c)  ((unsigned)((c) - '0') < 10)

/* \brief 板卡名称 */
#ifndef WED_BOARD_NAME
#define WED_BOARD_NAME                      ""
#endif

/*******************************************************************************
 * Code
 ******************************************************************************/
/**
 * \brief 从字符串中解析并跳过连续的数字字符，将其转换为对应的整数值
 */
static __inline int __skip_atoi(const char **s){
    register int i = 0;

    while (isdigit(**s)) {
        i = i * 10 + *((*s)++) - '0';
    }
    return i;
}

/**
 * \brief 计算字符串长度
 */
static unsigned long __strlen(const char *s){
    const char *sc;

    for (sc = s; *sc != '\0'; ++sc) /* nothing */
        ;

    return sc - s;
}

/**
 * \brief 执行除法运算
 */
#if WED_PRINT_LONGLONG
static __inline int __divide(long long *n, int base){
    int res;

    /* 针对不支持除法指令的处理器的优化 */
    if (base == 10) {
        res = (int)(((unsigned long long)*n) % 10U);
        *n = (long long)(((unsigned long long)*n) / 10U);
    } else {
        res = (int)(((unsigned long long)*n) % 16U);
        *n = (long long)(((unsigned long long)*n) / 16U);
    }

    return res;
}
#else
static __inline int __divide(long *n, int base){
    int res;

    /* 针对不支持除法指令的处理器的优化 */
    if (base == 10) {
        res = (int)(((unsigned long)*n) % 10U);
        *n = (long)(((unsigned long)*n) / 10U);
    } else {
        res = (int)(((unsigned long)*n) % 16U);
        *n = (long)(((unsigned long)*n) / 16U);
    }

    return res;
}
#endif

/**
 * \brief 数字打印
 */
#if WED_PRINT_PRECISION
static char *__number_print(char      *p_buf,
                            char      *p_end,
#if WED_PRINT_LONGLONG
                            long long  num,
#else
                            long       num,
#endif
                            int        base,
                            int        s,
                            int        precision,
                            int        type) {
#else
static char *__number_print(char      *p_buf,
                            char      *p_end,
#if WED_PRINT_LONGLONG
                            long long  num,
#else
                            long       num,
#endif
                            int        base,
                            int        s,
                            int        type) {
#endif
    char               c, sign;
#if WED_PRINT_LONGLONG
    char               tmp[32];
#else
    char               tmp[16];
#endif
    int                precision_bak  = precision;
    const char        *p_digits;
    static const char  small_digits[] = "0123456789abcdef";
    static const char  large_digits[] = "0123456789ABCDEF";
    register int       i;
    register int       size;

    size = s;

    p_digits = (type & LARGE) ? large_digits : small_digits;
    if (type & LEFT) {
        type &= ~ZEROPAD;
    }
    c = (type & ZEROPAD) ? '0' : ' ';

    /* get sign */
    sign = 0;
    if (type & SIGN) {
        if (num < 0) {
            sign = '-';
            num = -num;
        } else if (type & PLUS) {
            sign = '+';
        } else if (type & SPACE) {
            sign = ' ';
        }
    }

#if WED_PRINT_SPECIAL
    if (type & SPECIAL) {
        if (base == 16) {
            size -= 2;
        } else if (base == 8) {
            size--;
        }
    }
#endif

    i = 0;
    if (num == 0) {
        tmp[i++] = '0';
    } else {
        while (num != 0) {
            tmp[i++] = p_digits[__divide(&num, base)];
        }
    }

#if WED_PRINT_PRECISION
    if (i > precision) {
        precision = i;
    }
    size -= precision;
#else
    size -= i;
#endif

    if (!(type & (ZEROPAD | LEFT))) {
        if ((sign) && (size > 0)) {
            size--;
        }

        while (size-- > 0) {
            if (p_buf < p_end) {
                *p_buf = ' ';
            }
            ++p_buf;
        }
    }

    if (sign) {
        if (p_buf < p_end) {
            *p_buf = sign;
        }
        --size;
        ++p_buf;
    }

#if WED_PRINT_SPECIAL
    if (type & SPECIAL) {
        if (base == 8) {
            if (p_buf < p_end) {
                *p_buf = '0';
            }
            ++p_buf;
        } else if (base == 16) {
            if (p_buf < p_end) {
                *p_buf = '0';
            }
            ++p_buf;
            if (p_buf < p_end) {
                *p_buf = type & LARGE ? 'X' : 'x';
            }
            ++p_buf;
        }
    }
#endif

    /* no align to the left */
    if (!(type & LEFT)) {
        while (size-- > 0) {
            if (p_buf < p_end) {
                *p_buf = c;
            }
            ++p_buf;
        }
    }

#if WED_PRINT_PRECISION
    while (i < precision--) {
        if (p_buf < p_end) {
            *p_buf = '0';
        }
        ++p_buf;
    }
#endif

    /* put number in the temporary buffer */
    while (i-- > 0 && (precision_bak != 0)) {
        if (p_buf < p_end) {
            *p_buf = tmp[i];
        }
        ++p_buf;
    }

    while (size-- > 0) {
        if (p_buf < p_end) {
            *p_buf = ' ';
        }
        ++p_buf;
    }

    return p_buf;
}

/**
 * \brief 格式化输出函数
 *
 * @param p_buf 目标缓存地址
 * @param size  目标缓存大小
 * @param fmt   格式化字符串
 * @param args  可变参数列表
 *
 * @return 返回写入的字符数
 */
int Wed_vsnprintf(char          *p_buf,
                  unsigned long  size,
                  const char    *fmt,
                  va_list        args){
#if Wed_PRINT_LONGLONG
    unsigned long long num;
#else
    uint32_t           num;
#endif
    int                i, len;
    char              *p_str, *p_end, c;
    const char        *p_s;

    uint8_t            base;            /* 数值的进制基数 */
    uint8_t            flags;           /* 打印数字标志 */
    uint8_t            qualifier;       /* 整数域的 'h', 'l', 或 'L' 限定符 */
    int32_t            field_width;     /* 输出域宽度 */

#if WED_PRINT_PRECISION
    int                precision;       /* 精度：整数最小位数/字符串最大长度 */
#endif

    p_str = p_buf;
    p_end = p_buf + size;

    /* 防止越界 */
    if (p_end < p_buf) {
        p_end = ((char *) - 1);
        size  = p_end - p_buf;
    }

    for (; *fmt ; ++fmt) {
        /* 非格式化字符直接复制 */
        if (*fmt != '%') {
            if (p_str < p_end) {
                *p_str = *fmt;
            }
            ++p_str;
            continue;
        }

        /* 初始化格式标志位 */
        flags = 0;

        /* 解析格式标志位 */
        while (1) {
            /* 跳过第一个 '%' */
            ++fmt;
            if (*fmt == '-') {
                /* 左对齐 */
                flags |= LEFT;
            } else if (*fmt == '+') {
                /* 显示数字符号 */
                flags |= PLUS;
            } else if (*fmt == ' ') {
                /* 正数前加空格 */
                flags |= SPACE;
            } else if (*fmt == '#') {
                /* 特殊进制前缀（0x 等） */
                flags |= SPECIAL;
            } else if (*fmt == '0') {
                /* 用 0 填充 */
                flags |= ZEROPAD;
            } else {
                break;
            }
        }

        /* 解析域宽 */
        field_width = -1;

        /* 是否是 0-9 */
        if (isdigit(*fmt)) {
            /* 获取数字 */
            field_width = __skip_atoi(&fmt);
        } else if (*fmt == '*') {
            ++fmt;
            /* 从参数获取域宽 */
            field_width = va_arg(args, int);
            /* 负域宽视为左对齐 */
            if (field_width < 0) {
                field_width = -field_width;
                flags |= LEFT;
            }
        }

#if WED_PRINT_PRECISION
        /* 解析精度 */
        precision = -1;
        if (*fmt == '.') {
            ++fmt;

            /* 是否是 0-9 */
            if (isdigit(*fmt)) {
                /* 获取精度数字 */
                precision = __skip_atoi(&fmt);
            } else if (*fmt == '*') {
                ++fmt;
                /* 从参数获取域宽 */
                precision = va_arg(args, int);
            }
            /* 无效精度置 0 */
            if (precision < 0) {
                precision = 0;
            }
        }
#endif
        /* 获取转换限定符 */
        qualifier = 0;
#if WED_PRINT_LONGLONG
        if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L') {
#else
        if (*fmt == 'h' || *fmt == 'l') {
#endif
            qualifier = *fmt;
            ++fmt;
#if WED_PRINT_LONGLONG
            /* 'll' 表示long long */
            if (qualifier == 'l' && *fmt == 'l') {
                qualifier = 'L';
                ++fmt;
            }
#endif
        }

        /* 默认十进制 */
        base = 10;

        switch (*fmt) {
            /* 字符处理 */
            case 'c':
                if (!(flags & LEFT)) {

                    while (--field_width > 0) {
                        if (p_str < p_end) {
                            *p_str = ' ';
                        }
                        ++p_str;
                    }
                }

                /* 获取字符 */
                c = (uint8_t)va_arg(args, int);
                if (p_str < p_end) {
                    *p_str = c;
                }
                ++p_str;

                /* put width */
                while (--field_width > 0) {
                    if (p_str < p_end) {
                        *p_str = ' ';
                    }
                    ++p_str;
                }
                continue;

            case 's':
                p_s = va_arg(args, char *);
                if (!p_s) {
                    p_s = "(NULL)";
                }

                len = __strlen(p_s);
#if WED_PRINT_PRECISION
                if (precision > 0 && len > precision) {
                    len = precision;
                }
#endif

                if (!(flags & LEFT)) {
                    while (len < field_width--) {
                        if (p_str < p_end) {
                            *p_str = ' ';
                        }
                        ++p_str;
                    }
                }

                for (i = 0; i < len; ++i) {
                    if (p_str < p_end) {
                        *p_str = *p_s;
                    }
                    ++p_str;
                    ++p_s;
                }

                while (len < field_width--) {
                    if (p_str < p_end) {
                        *p_str = ' ';
                    }
                    ++p_str;
                }
                continue;

            case 'p':
                if (field_width == -1) {
                    field_width = sizeof(void *) << 1;
                    flags |= ZEROPAD;
                }
#if WED_PRINT_PRECISION
                p_str = __number_print(p_str,
                                       p_end,
                                      (long)va_arg(args, void *),
                                       16,
                                       field_width,
                                       precision,
                                       flags);
#else
                p_str = __number_print(p_str,
                                       p_end,
                                      (long)va_arg(args, void *),
                                       16,
                                       field_width,
                                       flags);
#endif
                continue;

            case '%':
                if (p_str < p_end) {
                    *p_str = '%';
                }
                ++p_str;
                continue;

            /* integer number formats - set up the flags and "break" */
            case 'o':
                base = 8;
                break;

            case 'X':
                flags |= LARGE;
            case 'x':
                base = 16;
                break;

            case 'd':
            case 'i':
                flags |= SIGN;
            case 'u':
                break;

            default:
                if (p_str < p_end) {
                    *p_str = '%';
                }
                ++p_str;

                if (*fmt) {
                    if (p_str < p_end) {
                        *p_str = *fmt;
                    }
                    ++p_str;
                } else {
                    --fmt;
                }
                continue;
        }

#if WED_PRINT_LONGLONG
        if (qualifier == 'L') {
            num = va_arg(args, long long);
        } else if (qualifier == 'l') {
#else
        if (qualifier == 'l') {
#endif
            num = va_arg(args, uint32_t);
            if (flags & SIGN) {
                num = (int32_t)num;
            }
        } else if (qualifier == 'h') {
            num = (uint16_t)va_arg(args, int32_t);
            if (flags & SIGN) {
                num = (int16_t)num;
            }
        } else {
            num = va_arg(args, uint32_t);
            if (flags & SIGN) {
                num = (int32_t)num;
            }
        }
#if WED_PRINT_PRECISION
        p_str = __number_print(p_str, p_end, num, base, field_width, precision, flags);
#else
        p_str = __number_print(p_str, p_end, num, base, field_width, flags);
#endif
    }

    if (size > 0) {
        if (p_str < p_end) {
            *p_str = '\0';
        } else {
            p_end[-1] = '\0';
        }
    }

    /* 末尾的空字节不计入总数 */
    return p_str - p_buf;
}

/**
 * \brief 打印函数
 *
 * @param fmt 打印内容
 */
void Wed_kprintf(const char *fmt, ...){
    int           ret;
    va_list       args;
    unsigned long length;

    if (__g_is_init == 0) {
        return;
    }

    ret = Wed_mutex_lock(&__g_print_lock, 5000);
    if (ret != 0) {
        return;
    }

    va_start(args, fmt);

    /* vsnprintf 函数的返回值是：假设缓冲区大小足够时，
     * 将被写入缓冲区的字节数（不包括终止的空字节'\0'）。
     * 如果输出字符串的长度超过了 WED_PRINT_BUF_SIZE，则必须调整输出长度。*/
    length = Wed_vsnprintf(__g_print_buf, WED_PRINT_BUF_SIZE - 1, fmt, args);
    if (length > WED_PRINT_BUF_SIZE - 1) {
        length = WED_PRINT_BUF_SIZE - 1;
    }

    Wed_dev_write(__g_p_dev_print, (uint8_t *)__g_print_buf, length);

    Wed_mutex_unlock(&__g_print_lock);

    va_end(args);
}

/**
 * \brief 打印函数
 *
 * @param fmt  打印内容
 * @param args 参数列表
 */
void Wed_vprintf(const char *fmt, va_list args){
    int           ret;
    unsigned long length;

    if (__g_is_init == 0) {
        return;
    }

    ret = Wed_mutex_lock(&__g_print_lock, 5000);
    if (ret != 0) {
        return;
    }

    /* vsnprintf 函数的返回值是：假设缓冲区大小足够时，
     * 将被写入缓冲区的字节数（不包括终止的空字节'\0'）。
     * 如果输出字符串的长度超过了 WED_PRINT_BUF_SIZE，则必须调整输出长度。*/
    length = Wed_vsnprintf(__g_print_buf, WED_PRINT_BUF_SIZE - 1, fmt, args);
    if (length > WED_PRINT_BUF_SIZE - 1) {
        length = WED_PRINT_BUF_SIZE - 1;
    }

    Wed_dev_write(__g_p_dev_print, (uint8_t *)__g_print_buf, length);

    Wed_mutex_unlock(&__g_print_lock);
}

/**
 * \brief 打印设备注册
 *
 * @param p_dev_name 设备名
 * @param p_cfg      设备配置
 *
 * @return 成功返回 0
 */
int Wed_print_dev_regsiter(char *p_dev_name){
    struct Wed_dev *p_dev = NULL;

    if (p_dev_name == NULL) {
        return -WED_EINVAL;
    }

    p_dev = Wed_dev_find_by_name(p_dev_name);
    if (p_dev == NULL) {
        return -WED_ENODEV;
    }
    __g_p_dev_print = p_dev;

    return 0;
}

/**
 * \brief 打印初始化
 *
 * @return 成功返回 0
 */
int Wed_print_init(void){
    int ret = 0;

    if (__g_p_dev_print) {
        if (__g_p_dev_print->p_opts->p_fn_init) {
            ret = __g_p_dev_print->p_opts->p_fn_init(__g_p_dev_print->p_opts_arg);
        }
    }

    if (ret == 0) {
        ret = Wed_mutex_init(&__g_print_lock);
        if (ret == 0) {
            __g_is_init = 1;

            Wed_kprintf("\r\n\r\n************ Wednesday OS ***********\r\n");
            Wed_kprintf("* Framework Version : %s\r\n", WED_VERSION);
            Wed_kprintf("* Board Name        : %s\r\n\r\n", WED_BOARD_NAME);

            WED_INFO_PRINT("print init success");

            return 0;
        }
    }
    WED_WARN_PRINT("print init failed(%d)", ret);

    return ret;
}
